iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0
Software Development

一個好的系統之好維護基本篇 ( 馬克版 )系列 第 11

Day-11: 實務時 Code Review 看 Class 地方 2 ( 封裝 )

  • 分享至 

  • xImage
  •  

同步至 medium

https://ithelp.ithome.com.tw/upload/images/20240925/20089358GwY7VnfDxh.png


16. 類別有沒有暴露外面不需要知道的東西

就是類別將內部在使用的方法和屬性讓外部可以取得,然後這個壞味道可能會產生一些問題 :

  • 外部依賴內部實現,可能會內部修改炸壞了外部。
  • 可能會讓外部誤用到本來只給內部用的方法,導致不可預期的事情 ( 例如那個內部方法只能在某個條件後才能執行,然後外部不知道,用了就炸了 )

下面為一個不好的範例,它就是將 balance 公開出去,而且可以被修改。

class BankAccount {
  // balance 是公開屬性,外部可以直接存取和修改
  public balance: number;

  constructor(initialBalance: number) {
    this.balance = initialBalance;
  }

  // 存款方法
  public deposit(amount: number): void {
    this.balance += amount;
  }

  // 提款方法
  public withdraw(amount: number): void {
    this.balance -= amount; 
  }
}

// 外部誤用
account.balance = 5000;

看點 17. 為了測試而將 private 方法改成 public

這裡我有看過不少人這樣做過,這個方法如果為了測試而公開,他就有可能被外部誤用。所以這裡如果看到這個問題,通常會給 1 個建議 :

測試包含這個方法的公有方法。

然後我有看過一種情況就是,因為原本使用的公有方法太複雜,例如 checkout 結帳用的方法,然後將所有結帳前與後,要做的整間公司 domain 都放在裡面,所以導致他太巨大,然後要在裡面新加一個功能,結果就是要測試時,需要產很多資料,所以有不少人就會想說直接測試那個方法。

老實說,我自已也做過這個事情,因為光要產生那個方法所需要的資料,我都不知道要花多少時間了,因為太多資料關聯性要理解,所以在 deadline 與疲勞的雙重壓力下就改成 public 了。

但事實上問題的根源就只是那個方法職責太多了,所以建議下次看到這個問題,如果你真的有 deadline 壓力,那我可能睜一隻眼閉一隻眼,但是我一定要在那個地方看到TODO 與 task 票喔 ~

~小心得~
有時後也不要很強硬的 code review 時看到這種就要求直接拆與重構。

因為很多情況下有 deadline 且可能很多地方都不是那個人寫的,只是他運氣不好要改那裡,所以這裡以我 tech leader 角色,通常不強硬要求,但至少會做到上面說的開 task追蹤重構。

至於團隊有沒有重構處理那張票的流程,讓程式碼 be better,就真的是 tech leader 要想辦法與 PM、營運們協商導入流程了。


看點 18. 方法名曝露實作細節

例如下面範例的 getUserFromDatabaseById,如果這時多加個 cache 怎麼是不是又要改了呢 ?

class UserService {
  // 它暴露了內部的存儲細節,是使用 database
  public getUserFromDatabaseById(id: number): { userId: number; userName: string; } | null {
    return user ? { userId: user.id, userName: user.name } : null;
  }
}

// 使用範例
const userService = new UserService();
console.log(userService.getUserFromDatabaseById(1));

看點 19. 回傳曝露實作細節

大概如下程式碼,這個方法內部使用 http client 來取得到相關的資料,方法名沒有曝露實作細節,但是問題是出在回傳曝露他是使用 http,因為他回傳了 httpCode,如果這時改成用 db 的話,那是不是外面使用 httpCode 來驗證的地方就要全改了 ?

class UserService {
  private httpClient: HttpClient;

  constructor(httpClient: HttpClient) {
    this.httpClient = httpClient;
  }

  // 方法內部使用 httpClient 來獲取用戶數據,並且回傳 httpCode,這暴露了實作細節
  public async getUserById(id: number): Promise<{ user: User | null, httpCode: number }> {
    try {
      const response = await this.httpClient.get(`/users/${id}`);
      if (response.status === 200) {
        const userData = response.data;
        return {
          user: new User(userData.id, userData.name),
          httpCode: response.status
        };
      } else {
        return { user: null, httpCode: response.status };
      }
    } catch (error) {
      return { user: null, httpCode: 500 };
    }
  }
}

看點 20. 迪米特法則(Demeter Law)

這個原則的概念為如下,它又有另一個名稱叫最少知識原則

一個類別對其它類別應該知道的越少越好

然後比較工程師的說,就是一個物件只能與以下的東西進行溝通:

  1. 自身的屬性和方法
  2. 傳入方法的參數
  3. 自已耦合的物件

然後的重點就是 :

不要使用自已耦合的物件裡面的東西
備註: 但還是有特例,例如 proxy 的 class,他本身就是個代理,你叫他不能用依賴物件的方法也很奇怪。

因為如果可以使用耦合的物件裡面的東西,然後裡面又再使用裡面的東西,那是不是那條耦合線就不斷的拉長,所以這個限制就是要將那條線給砍斷。

如下範例程式碼。

// Bad
class Address {
  constructor(public city: string) {}
}

class Customer {
  constructor(public address: Address) {}
}

class Order {
  constructor(public customer: Customer) {}

  // 違反 Demeter 法則的情況
  public getCustomerCity(): string {
    // 這裡跨越了多個對象的邊界
    return this.customer.address.city;
  }
}

// Good
class Address {
  constructor(public city: string) {}
}

class Customer {
  constructor(private address: Address) {}

  // 提供一個方法,讓外部訪問 city,而不是直接暴露 address
  public getCity(): string {
    return this.address.city;
  }
}

class Order {
  constructor(private customer: Customer) {}

  // 符合 Demeter 法則
  public getCustomerCity(): string {
    // 直接從 customer 取得 city,不需要了解其內部結構
    return this.customer.getCity();
  }
}

小結

本篇文章中,咱們討論了以下五個看點 :

  • 看點 16. 類別有沒有暴露外面不需要知道的東西
  • 看點 17. 為了測試而將 private 方法改成 public
  • 看點 18. 方法名與回傳曝露實作細節
  • 看點 19. 回傳曝露實作細節
  • 看點 20. 迪米特法則(Demeter Law)

這 5 個看點事實上主要都是討論 :

類別的封裝

一個好的封裝有幾個特點 :

  1. 不必要地暴露內部細節,因為可以減少外部的依賴細節的變動,所影響的變動
  2. 提供好的對外 interface 但不要包含實作細節。
  3. 注意回傳與錯誤處理時,有沒有曝露到實作細節。

這個說來說去又是 SRP 這張圖,好的封裝就是可以降低左邊用戶的變動源。

https://ithelp.ithome.com.tw/upload/images/20240925/20089358AhncjcWFAT.png


上一篇
Day-10: 實務時 Code Review 看 Class 地方 1 ( 基本 )
下一篇
Day-12: 實務時 Code Review 時會看的地方 ( 錯誤處理 )
系列文
一個好的系統之好維護基本篇 ( 馬克版 )30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言